

/*
 * Copyright (c) 2021. Dylan Cai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

@file:Suppress("unused")

//package com.dylanc.longan

package com.gitee.wsl.ext.string

import com.gitee.wsl.ext.string.builder.toAttributeName
import timber.log.Timber
import kotlin.jvm.JvmStatic


val String.Companion.EMPTY: String get() = ""

val String.Companion.SPACE_SEPARATOR: String get() = " "




object StringExt{

    /**
     * The empty String `""`.
     * @since 2.0
     */
    //const val EMPTY = ""

// Performance testing notes (JDK 1.4, Jul03, scolebourne)
// Whitespace:
// Character.isWhitespace() is faster than WHITESPACE.indexOf()
// where WHITESPACE is a string of all whitespace characters
//
// Character access:
// String.charAt(n) versus toCharArray(), then array[n]
// String.charAt(n) is about 15% worse for a 10K string
// They are about equal for a length 50 string
// String.charAt(n) is about 4 times better for a length 3 string
// String.charAt(n) is best bet overall
//
// Append:
// String.concat about twice as fast as StringBuffer.append
// (not sure who tested this)
    /**
     * A String for a space character.
     *
     * @since 3.2
     */
    //const val SPACE = " "

    /**
     * A String for linefeed LF ("\n").
     *
     * @see [JLF: Escape Sequences
     * for Character and String Literals](http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html.jls-3.10.6)
     *
     * @since 3.2
     */
    //const val LF = "\n"

    /**
     * A String for carriage return CR ("\r").
     *
     * @see [JLF: Escape Sequences
     * for Character and String Literals](http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html.jls-3.10.6)
     *
     * @since 3.2
     */
    //const val CR = "\r"

    /**
     * 用于随机选的数字
     */
    const val BASE_NUMBER = "0123456789"

    /**
     * 用于随机选的字符
     */
    const val BASE_CHAR = "abcdefghijklmnopqrstuvwxyz"

    /**
     * 用于随机选的字符和数字（小写）
     */
    const val BASE_CHAR_NUMBER_LOWER = BASE_CHAR + BASE_NUMBER

    /**
     * 用于随机选的字符和数字（包括大写和小写字母）
     */
    val BASE_CHAR_NUMBER = BASE_CHAR.uppercase() + BASE_CHAR_NUMBER_LOWER


    /**
     * Method creates variation by replacing characterToReplace with all allowedReplacements (one at a time)
     *
     * @param originalTexts       texts from which variation are to be made
     * @param characterToReplace  make variations by replacing this character in originalText
     * @param allowedReplacements replace characterToReplace with each of these characters
     * @return all unique variations
     */
    @JvmStatic
    fun makeVariations(
        originalTexts: List<String>,
        characterToReplace: Char,
        vararg allowedReplacements: Char,
    ): Set<String> {
        val result: MutableSet<String> = HashSet()
        for (originalText in originalTexts) {
            result.add(originalText)
            for (replacement in allowedReplacements) {
                result.add(originalText.replace(characterToReplace, replacement))
            }
        }
        return result
    }

    /**
     * 字符串数据库字符转义
     * @param str 待处理字符串
     * @return 转义处理后的字符串
     */
    fun sqliteEscape(str: String): String {
        if (str.isEmpty()) return str
        var keyWord = str
        // 替换关键字
        keyWord = keyWord.replace("/", "//")
        keyWord = keyWord.replace("'", "''")
        keyWord = keyWord.replace("[", "/[")
        keyWord = keyWord.replace("]", "/]")
        keyWord = keyWord.replace("%", "/%")
        keyWord = keyWord.replace("&", "/&")
        keyWord = keyWord.replace("_", "/_")
        keyWord = keyWord.replace("(", "/(")
        keyWord = keyWord.replace(")", "/)")
        return keyWord
    }



    /**
     * 裁剪超出的内容, 并且追加符号 ( 如 ... )
     * @param maxLength 允许最大的长度
     * @param str       待处理字符串
     * @param symbol    转换符号
     * @return 处理后的字符串
     */
    fun subEllipsize(
        maxLength: Int,
        str: String,
        symbol: String = "...",
    ): String {
        if (maxLength >= 1) {
            // 获取内容长度
            val strLength: Int = str.length
            // 防止为不存在数据
            if (strLength != 0) {
                return if (maxLength >= strLength) {
                    str
                } else str.substring(0, maxLength) + symbol
            }
        }
        return ""
    }

    /**
     * 裁剪符号处理
     * @param start        开始位置
     * @param symbolNumber 转换数量
     * @param str          待处理字符串
     * @param symbol       转换符号
     * @return 处理后的字符串
     */
    fun subSymbolHide(
        start: Int,
        symbolNumber: Int,
        str: String,
        symbol: String = " ...",
    ): String {
        if (str.isNotEmpty()) {
            if (start <= 0 || symbolNumber <= 0) {
                return str
            }
            // 获取数据长度
            val length = str.length
            // 如果数据小于 start 位则直接返回
            return if (length <= start) {
                str
            } else { // 大于 start 位
                val builder = StringBuilder()
                builder.append(str.substring(0, start))
                var len = length - start - symbolNumber
                // 如果超出总长度, 则进行控制
                len = if (len <= 0) { // 表示后面的全部转换
                    length - start
                } else { // 需要裁剪的数量
                    symbolNumber
                }
                // 进行遍历保存
                for (i in 0 until len) {
                    builder.append(symbol)
                }
                builder.append(str.substring(start + len, length))
                builder.toString()
            }
        }
        return ""
    }

    /**
     * 裁剪内容 ( 设置符号处理 )
     * @param str               待处理字符串
     * @param frontRetainLength 前面保留的长度
     * @param rearRetainLength  后面保留的长度
     * @param symbol            转换符号
     * @return 处理后的字符串
     */
    fun subSetSymbol(
        str: String,
        frontRetainLength: Int,
        rearRetainLength: Int,
        symbol: String = " ...",
    ): String? {
        try {
                // 截取前面需保留的内容
                val startStr = str.substring(0, frontRetainLength)
                // 截取后面需保留的内容
                val endStr = str.substring(str.length - rearRetainLength)
                // 特殊符号长度
                val symbolLength = str.length - (frontRetainLength + rearRetainLength)
                if (symbolLength >= 1) {
                    val builder = StringBuilder()
                    for (i in 0 until symbolLength) {
                        builder.append(symbol)
                    }
                    return startStr + builder.toString() + endStr
                }
                return startStr + endStr
            } catch (e: Exception) {
                Timber.d(e, "subSetSymbol")
            }
        return null
    }


    /**
     * 通用匹配函数
     * @param regex 正则表达式
     * @param input 待校验的字符串
     * @return `true` yes, `false` no
     */
    fun match(
        regex: String,
        input: String,
    ): Boolean {
        if (input.isNotEmpty()) {
            try {
                //return   Pattern.matches(regex, input)
                return input.matches(regex.toRegex())
            } catch (e: Exception) {
                Timber.d(e, "match")
            }
        }
        return false
    }


    fun lenientFormat(
        template: String?, vararg args: Any,
    ): String {
        val template = template?:"null"
        val argStrings  = mutableListOf<String>()
        for (i in args.indices) {
           argStrings.add(lenientToString(args[i]))
         }

        // start substituting the arguments into the '%s' placeholders
        val builder = StringBuilder(template.length + 16 * argStrings.size)
        var templateStart = 0
        var i = 0
        while (i < argStrings.size) {
            val placeholderStart = template.indexOf("%s", templateStart)
            if (placeholderStart == -1) {
                break
            }
            builder.append(template, templateStart, placeholderStart)
            builder.append(argStrings[i++])
            templateStart = placeholderStart + 2
        }
        builder.append(template, templateStart, template.length)

        // if we run out of placeholders, append the extra args in square braces
        if (i < argStrings.size) {
            builder.append(" [")
            builder.append(argStrings[i++])
            while (i < argStrings.size) {
                builder.append(", ")
                builder.append(argStrings[i++])
            }
            builder.append(']')
        }
        return builder.toString()
    }

    private fun lenientToString(o: Any): String {
        return try {
            o.toString()
        } catch (e: Exception) {
            "Exception during lenientFormat for $e"
        }
        /*return  try {
            o.toString()
        } catch (e: Exception) {
            // Default toString() behavior - see Object.toString()
            val objectToString = o.javaClass.name + '@' + Integer.toHexString(System.identityHashCode(o))
            // Logger is created inline with fixed name to avoid forcing Proguard to create another class.
           Timber.w("Exception during lenientFormat for $objectToString", e)
            "<" + objectToString + " threw " + e.javaClass.name + ">"
        }*/
    }

    /**
     *
     * Checks if any of the CharSequences are empty ("") or null.
     *
     * <pre>
     * StringUtils.isAnyEmpty((String) null)    = true
     * StringUtils.isAnyEmpty((String[]) null)  = false
     * StringUtils.isAnyEmpty(null, "foo")      = true
     * StringUtils.isAnyEmpty("", "bar")        = true
     * StringUtils.isAnyEmpty("bob", "")        = true
     * StringUtils.isAnyEmpty("  bob  ", null)  = true
     * StringUtils.isAnyEmpty(" ", "bar")       = false
     * StringUtils.isAnyEmpty("foo", "bar")     = false
     * StringUtils.isAnyEmpty(new String[]{})   = false
     * StringUtils.isAnyEmpty(new String[]{""}) = true
    </pre> *
     *
     * @param css  the CharSequences to check, may be null or empty
     * @return `true` if any of the CharSequences are empty or null
     * @since 3.2
     */
    fun isAnyEmpty(vararg css: CharSequence): Boolean {
        if (css.isEmpty()) {
            return false
        }
        for (cs in css) {
            if (cs.isEmpty()) {
                return true
            }
        }
        return false
    }

}

fun String.nullIfBlank(): String? = ifBlank { null }

fun String.Companion.empty() = String.EMPTY

fun String.Companion.nullToEmpty(value: String?): String = value ?: String.EMPTY

operator fun String.div(other:String) = "$this/$other"

fun String.toAttributeName(): String = usingAppendable(Appendable::toAttributeName)

fun String.usingAppendable(function: Appendable.(CharSequence) -> Unit): String =
    StringBuilder().also { it.function(this) }.toString()

/**
 * just a newline character on different platforms/targets
 *
 * @return newline
 */
fun newLineChar(): Char = '\n'




